﻿using System;
using System.Collections.Generic;
using System.Linq;
using BFlatCompile.Nodes;

namespace BFlatCompile.Serializer
{
	internal static class Phase22SerializeClasses
	{
		private const int SIG_CONSTRUCTOR = 1;
		private const int SIG_FIELD = 2;
		private const int SIG_METHOD = 3;
		private const int SIG_PROPERTY_GET = 4;
		private const int SIG_PROPERTY_SET = 5;
		private const int SIG_STATIC_CONSTRUCTOR = 6;
		private const int SIG_STATIC_FIELD = 7;
		private const int SIG_STATIC_METHOD = 8;
		private const int SIG_STATIC_PROPERTY_GET = 9;
		private const int SIG_STATIC_PROPERTY_SET = 10;
		private const int SIG_NESTED_CLASS = 11;

		public static string[] Do(IList<Class> classes, Dictionary<object, object> parents, Dictionary<string, int> startLocation, Compiler compiler)
		{
			List<string> basicOutput = new List<string>();
			List<string> memberListOutput = new List<string>();
			List<string> baseClassIdChainOutput = new List<string>();

			foreach (Class c in classes)
			{
				int nextConstructorId = 0;

				// base class chain
				List<string> baseClassChain = new List<string>();
				for (Class currentClass = c; currentClass != null; currentClass = currentClass.BaseClassInstance)
				{
					baseClassChain.Add(Util.ToHex(compiler, currentClass.ID));

					if (currentClass != c &&
						nextConstructorId == 0)
					{
						foreach (Constructor constructor in currentClass.Constructors)
						{
							if (!constructor.IsStatic)
							{
								nextConstructorId = constructor.ID;
							}
						}
					}
				}
				baseClassChain[0] = Util.ToHex(compiler, baseClassChain.Count - 1);
				baseClassIdChainOutput.Add(string.Join(Util.GetRowDelimiter(compiler), baseClassChain));

				// class members
				Dictionary<int, int[]> stepIdToMemberInfo = new Dictionary<int, int[]>();
				int[] staticInit = null;
				// GetMembers gets from the most abstract of base classes first, so 
				// adding everything into a dictionary that overwrites previous keys
				// is okay.
				foreach (object member in c.GetMembers(true, true, true)) // TODO: change this to false and get rid of the stupid hack below for the static initializer
				{
					// CreateMemberClassLink usually returns 1 set of info, but returns
					// 2 in the case of properties that have both a setter and getter.
					foreach (int[] nameAndTypeAndId in CreateMemberClassLink(member))
					{
						int name = nameAndTypeAndId[0];
						int type = nameAndTypeAndId[1];
						if (type == SIG_PROPERTY_SET || type == SIG_STATIC_PROPERTY_SET)
						{
							name *= -1; // this is a total hack. This is simply to prevent the
							// dictionary from letting the getters and setters 
							// clobber each other. The absolute value is stored
							// in the final table.
						}
						else if (type == SIG_STATIC_METHOD && name == -1)
						{
							// We add the static initializer to the lookup separately.
							// This is because of the absolute value hack above and the fact that the static
							// initializer's name step index is -1 which will get saved as 1 and will look like
							// a method with the name of whatever is step ID 1, in the metadata.
							staticInit = nameAndTypeAndId; 
							continue;
						}
						int id = nameAndTypeAndId[2];

						stepIdToMemberInfo[name] = nameAndTypeAndId;
					}
				}

				List<string> members = new List<string>();
				if (staticInit != null)
				{
					members.Add(Util.ToHex(compiler, -1));
					members.Add(Util.ToHex(compiler, staticInit[1]));
					members.Add(Util.ToHex(compiler, staticInit[2]));
				}

				foreach (int[] memberInfo in stepIdToMemberInfo.Values)
				{
					members.Add(Util.ToHex(compiler, System.Math.Abs(memberInfo[0])));
					members.Add(Util.ToHex(compiler, memberInfo[1]));
					members.Add(Util.ToHex(compiler, memberInfo[2]));
				}
				memberListOutput.Add(string.Join(Util.GetColDelimiter(compiler), members));

				// basic info
				int parentNamespaceId = 0;
				int parentClassId = 0;

				object parent = parents[c];
				if (parent is Class)
				{
					parentClassId = ((Class)parent).ID;
				}
				else if (parent is Namespace)
				{
					parentNamespaceId = ((Namespace)parent).ID;
				}
				else
				{
					throw new Exception("This shouldn't happen.");
				}

				int constructorId = 0;
				int staticConstructorId = 0;
				int staticInitializerId = c.StaticInitializer.ID;

				foreach (Constructor constructor in c.Constructors)
				{
					if (!constructor.IsStatic)
					{
						constructorId = constructor.ID;
					}
					else
					{
						staticConstructorId = constructor.ID;
					}
				}

				int baseClassID = c.BaseClassInstance == null ? 0 : c.BaseClassInstance.ID;

				basicOutput.Add(string.Join(Util.GetColDelimiter(compiler),
					Util.ToHex(compiler, c.NameIndex),
					Util.ToHex(compiler, parentNamespaceId),
					Util.ToHex(compiler, parentClassId),
					Util.ToHex(compiler, staticConstructorId),
					Util.ToHex(compiler, constructorId),
					Util.ToHex(compiler, nextConstructorId),
					Util.ToHex(compiler, staticInitializerId),
					Util.ToHex(compiler, baseClassID)));
			}

			return new string[] {
				string.Join(Util.GetRowDelimiter(compiler), basicOutput),
				string.Join(Util.GetRowDelimiter(compiler), memberListOutput),
				string.Join(Util.GetRowDelimiter(compiler), baseClassIdChainOutput)
			};
		}

		private static int[][] CreateMemberClassLink(object member)
		{
			if (member is Method)
			{
				Method m = (Method)member;
				return new int[][] { new int[] { m.NameIndex, (m.IsStatic ? SIG_STATIC_METHOD : SIG_METHOD), m.ID } };
			}

			if (member is Field)
			{
				Field f = (Field)member;
				return new int[][] { new int[] { f.NameIndex, (f.IsStatic ? SIG_STATIC_FIELD : SIG_FIELD), f.ID } };
			}

			if (member is Property)
			{
				Property p = (Property)member;
				List<int[]> output = new List<int[]>();
				if (p.Getter != null)
				{
					output.Add(new int[] { p.NameIndex, (p.IsStatic ? SIG_STATIC_PROPERTY_GET : SIG_PROPERTY_GET), p.GetterID });
				}

				if (p.Setter != null)
				{
					output.Add(new int[] { p.NameIndex, (p.IsStatic ? SIG_STATIC_PROPERTY_SET : SIG_PROPERTY_SET), p.SetterID });
				}

				return output.ToArray();
			}

			if (member is Constructor)
			{
				Constructor c = (Constructor)member;
				return new int[][] { }; //new int[] { 0, (c.IsStatic ? SIG_STATIC_CONSTRUCTOR: SIG_CONSTRUCTOR), c.ID } };
			}

			if (member is Class)
			{
				Class c = (Class)member;
				return new int[][] { new int[] { c.NameIndex, SIG_NESTED_CLASS, c.ID } };
			}

			return null;
		}
	}
}
